using System;
using System.Collections.Generic;
using System.Text;
using WMEncoderLib;
using Microsoft.Build.Utilities;
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace WMVConvert
{
	internal class Profile : IProfile, IDisposable
	{
		private IMediaInfo item;
		private IProfileConfig config;
		private IWMEncProfile2 wmeProfile;
		private TaskLoggingHelper log;

		private TaskLoggingHelper Log
		{
			get { return log; }
		}

		public Profile(TaskLoggingHelper log, IMediaInfo mediaInfo,IProfileConfig config)
		{
			this.log = log;
			this.config = config;
			this.item = mediaInfo;
			this.wmeProfile = new WMEncProfile2Class();
			this.Init();
		}

		private IWMEncProfile2 Init()
		{
			try
			{
				this.wmeProfile.Clear();
				this.wmeProfile.ProfileName = "WMVConvertProfile";
				if ((item.AudioCount > 0) && (item.VideoCount > 0))
				{
					this.wmeProfile.ContentType = Constants.WMENC_CONTENT_ONE_AUDIO_ONE_VIDEO;
					this.wmeProfile.set_VBRMode(WMENC_SOURCE_TYPE.WMENC_AUDIO, 0, config.AudioBitRateMode);
					this.wmeProfile.set_VBRMode(WMENC_SOURCE_TYPE.WMENC_VIDEO, 0, config.VideoBitRateMode);
				}
				else if (item.AudioCount > 0)
				{
					this.wmeProfile.ContentType = Constants.WMENC_CONTENT_ONE_AUDIO;
					this.wmeProfile.set_VBRMode(WMENC_SOURCE_TYPE.WMENC_AUDIO, 0, config.AudioBitRateMode);
				}
				else
				{
					if (item.VideoCount <= 0)
					{
						throw new InvalidOperationException("No Audio or Video stream detected");
					}
					this.wmeProfile.ContentType = Constants.WMENC_CONTENT_ONE_VIDEO;
					this.wmeProfile.set_VBRMode(WMENC_SOURCE_TYPE.WMENC_VIDEO, 0, config.VideoBitRateMode);
				}
			}
			catch (Exception exception)
			{
				Log.LogError(exception.Message);
				return null;
			}
			IWMEncAudienceObj audience = this.wmeProfile.AddAudience(100000);
			try
			{
				if (item.VideoCount > 0)
				{
					int lCodecIndex = VideoCodecIndex;
					audience.set_VideoCodec(0, lCodecIndex);
					audience.set_Property(WMENC_SOURCE_TYPE.WMENC_VIDEO, 0, "DeviceConformanceTarget", "");
					audience.set_VideoKeyFrameDistance(0, new decimal(config.VideoKeyFrameDistance));
					audience.set_VideoWidth(0, item.Width);
					audience.set_VideoHeight(0, item.Height);
					audience.set_VideoFPS(0, (int)VideoFrameRate);
					switch (config.VideoBitRateMode)
					{
						case WMENC_PROFILE_VBR_MODE.WMENC_PVM_NONE:
						case WMENC_PROFILE_VBR_MODE.WMENC_PVM_BITRATE_BASED:
							audience.set_VideoBitrate(0, Math.Min(item.VideoBitRate, this.config.VideoMaxBitRate));
							audience.set_VideoImageSharpness(0, config.VideoImageSharpness);
							audience.set_VideoBufferSize(0, config.VideoBufferSize);
							break;

						case WMENC_PROFILE_VBR_MODE.WMENC_PVM_PEAK:
							audience.set_VideoBitrate(0, item.VideoBitRate);
							audience.set_VideoPeakBitrate(0, item.VideoPeakBitRate);
							audience.set_VideoBufferMax(0, config.VideoBufferMax);
							break;

						case WMENC_PROFILE_VBR_MODE.WMENC_PVM_UNCONSTRAINED:
							audience.set_VideoCompressionQuality(0, config.VideoQuality);
							break;
					}
				}
				if (item.AudioCount > 0)
				{
					short audioResolution = 0;
					int audioBitRate = 0;
					int lCodecIndex = AudioCodecIndex;
					audience.set_AudioCodec(0, lCodecIndex);
					if (config.AudioBitRateMode != WMENC_PROFILE_VBR_MODE.WMENC_PVM_UNCONSTRAINED)
					{
						int delta = int.MaxValue;
						string codecName = string.Empty;
						int formatCount = this.wmeProfile.get_AudioFormatCount(lCodecIndex);
						for (int i = 0; i < formatCount; i++)
						{
							object pvarName = (object)string.Empty;
							object bitsPerChannel = 0;
							object audioChannels = 0;
							object samplingRate = 0L;
							int iItem = this.wmeProfile.EnumAudioFormat(lCodecIndex, i, out pvarName, out samplingRate, out audioChannels, out bitsPerChannel);
							if ((((short)item.Channels) == ((short)audioChannels)) && (((int)item.SamplingRate) == ((int)samplingRate)))
							{
								int foundDelta = this.AudioBitRate - iItem;
								if ( foundDelta<0 )
								{
									foundDelta = iItem - this.AudioBitRate;
								}
								if ( foundDelta <= delta )
								{
									codecName = pvarName.ToString();
									delta = foundDelta;
									audioResolution = (short)bitsPerChannel;
									audioBitRate = iItem;
								}
							}
						}
						if (delta == int.MaxValue)
						{
							throw new Exception("Windows Media does not support the audio format with the selected Bitrate.");
						}
						else
						{
							Trace.WriteLine("Valid Audio Format Found: {0}", codecName);
						}
					}
					switch (config.AudioBitRateMode)
					{
						case WMENC_PROFILE_VBR_MODE.WMENC_PVM_NONE:
						case WMENC_PROFILE_VBR_MODE.WMENC_PVM_BITRATE_BASED:
							//TODO:Not working
							audience.SetAudioConfig(0, (short)this.item.Channels, (int)item.SamplingRate, audioBitRate, audioResolution);
							break;

						case WMENC_PROFILE_VBR_MODE.WMENC_PVM_PEAK:
							break;

						case WMENC_PROFILE_VBR_MODE.WMENC_PVM_UNCONSTRAINED:
							audience.SetAudioConfig(0, (short)this.item.Channels, (int)item.SamplingRate, item.AudioBitRate, (short)item.Resolution);
							break;
					}
				}
			}
			catch (Exception exception)
			{
				Log.LogError(exception.Message);
				return wmeProfile;
			}
			try
			{
				this.wmeProfile.Validate();
			}
			catch (Exception exception)
			{
				Log.LogError(exception.Message);
			}
			return this.wmeProfile;
		}

		private string AudioCodec
		{
			get
			{
				if (this.item.Channels == 1)
				{
					return Constants.WMASTD;
				}
				else
				{
					return config.AudioCodec;
				}
			}
		}

		private int AudioCodecIndex
		{
			get
			{
				int lFourCC = 0;
				if (string.Compare(this.AudioCodec, Constants.WMASTD, false) == 0)
				{
					lFourCC = Constants.WMASTD_FOURCC;
				}
				else if (string.Compare(this.AudioCodec, Constants.WMAPRO, false) == 0)
				{
					lFourCC = Constants.WMAPRO_FOURCC;
				}
				else if (string.Compare(this.AudioCodec, Constants.WMALSL, false) == 0)
				{
					lFourCC = Constants.WMALSL_FOURCC;
				}
				else if (string.Compare(this.AudioCodec, Constants.WMSPEECH, false) == 0)
				{
					lFourCC = Constants.WMSPEECH_FOURCC;
				}
				else if (string.Compare(this.AudioCodec, Constants.PCM, false) == 0)
				{
					lFourCC = Constants.PCM_FOURCC;
				}
				else
				{
					lFourCC = Constants.WMASTD_FOURCC;
				}
				return this.wmeProfile.GetCodecIndexFromFourCC(WMENC_SOURCE_TYPE.WMENC_AUDIO, lFourCC);
			}
		}

		private int VideoCodecIndex
		{
			get
			{
				int lFourCC = 0;
				if (string.Compare(config.VideoCodec, Constants.WMV7, false) == 0)
				{
					lFourCC = Constants.WMV7_FOURCC;
				}
				else if (string.Compare(config.VideoCodec, Constants.WMV8, false) == 0)
				{
					lFourCC = Constants.WMV8_FOURCC;
				}
				else if (string.Compare(config.VideoCodec, Constants.WMV9, false) == 0)
				{
					lFourCC = Constants.WMV9_FOURCC;
				}
				else if (string.Compare(config.VideoCodec, Constants.WVC1, false) == 0)
				{
					lFourCC = Constants.WVC1_FOURCC;
				}
				else if (string.Compare(config.VideoCodec, Constants.WMS9, false) == 0)
				{
					lFourCC = Constants.WMS9_FOURCC;
				}
				else if (string.Compare(config.VideoCodec, Constants.MP41, false) == 0)
				{
					lFourCC = Constants.MP41_FOURCC;
				}
				else if (string.Compare(config.VideoCodec, Constants.UNCOMP, false) == 0)
				{
					lFourCC = Constants.UNCOMP_FOURCC;
				}
				else
				{
					lFourCC = Constants.WMV9_FOURCC;
				}
				return this.wmeProfile.GetCodecIndexFromFourCC(WMENC_SOURCE_TYPE.WMENC_VIDEO, lFourCC);
			}
		}

		private long VideoFrameRate
		{
			get { return (long)Math.Round(item.FrameRate * 1000); }
		}

		int[] audioBitRates = {
			0x158880,
			0xbb800,
			0xac440,
			0x9c400,
			0x6b6c0,
			0x5dc00,
			0x56220,
			0x4e200,
			0x3e800,
			0x2ee00,
			0x27100,
			0x1f400,
			0x17700,
			0x13880,
			0xfa00,
			0xbb80,
			0x9c40,
			0x7d00,
			0x55f0,
			0x4e20,
			0x4268,
			0x3e80,
			0x2ee0,
			0x2710,
			0x1f40,
			0x1770,
			0x1388,
			0xfa0
		};
		private int AudioBitRate
		{
			get
			{
				foreach (int bitRate in audioBitRates)
				{
					if (bitRate <= item.AudioBitRate)
					{
						return bitRate;
					}
				}
				return item.AudioBitRate;
			}
		}

		public int AudioPeakBitRate
		{
			get { throw new Exception("The method or operation is not implemented."); }
		}

		public WMEncoderLib.IWMEncProfile2 WMEProfile
		{
			get
			{
				if (wmeProfile == null)
				{
					wmeProfile = new WMEncProfile2Class();
					wmeProfile = Init();
				}
				return wmeProfile;
			}
		}

		#region Implement IDisposable

		bool disposed;

		// Do not make this method virtual.
		// A derived class should not be able to override this method.
		public void Dispose()
		{
			Dispose(true);
			// Take yourself off the Finalization queue 
			// to prevent finalization code for this object
			// from executing a second time.
			GC.SuppressFinalize(this);
		}

		// Dispose(bool disposing) executes in two distinct scenarios.
		// If disposing equals true, the method has been called directly
		// or indirectly by a user's code. Managed and unmanaged resources
		// can be disposed.
		// If disposing equals false, the method has been called by the 
		// runtime from inside the finalizer and you should not reference 
		// other objects. Only unmanaged resources can be disposed.
		protected virtual void Dispose(bool disposing)
		{
			// Check to see if Dispose has already been called.
			if (!this.disposed)
			{
				// If disposing equals true, dispose all managed 
				// and unmanaged resources.
				if (disposing)
				{
					// Dispose managed resources.
				}
				// Release unmanaged resources. If disposing is false, 
				// only the following code is executed.
				Marshal.ReleaseComObject(this.wmeProfile);
				this.wmeProfile = null;
				// Note that this is not thread safe.
				// Another thread could start disposing the object
				// after the managed resources are disposed,
				// but before the disposed flag is set to true.
				// If thread safety is necessary, it must be
				// implemented by the client.

			}
			disposed = true;
		}

		// Use C# destructor syntax for finalization code.
		// This destructor will run only if the Dispose method 
		// does not get called.
		// It gives your base class the opportunity to finalize.
		// Do not provide destructors in types derived from this class.
		~Profile()
		{
			// Do not re-create Dispose clean-up code here.
			// Calling Dispose(false) is optimal in terms of
			// readability and maintainability.
			Dispose(false);
		}

		#endregion
	}
}
